/**
 * 支持父级，自定义父级，以及window作为父级
 * 使用示例：
 * 1. v-dragable
 * 2. v-dragable="'father'" // 使用父级作为父级
 * 3. v-dragable="'body'" // 使用body对象作为父级
 * 4. v-dragable="'#app'" // 使用id作为父级
 * 5. v-dragable="'.list'" // 使用class名作为父级
 * 3-5代表所有可被document.querySelector()解析的参数值
 **/
import type {Directive, DirectiveBinding} from 'vue'

interface Position {
	x: number,
	y: number
}

interface Mouse {
	down: Position,
	move: Position
}

interface ElType extends HTMLElement {
	__mouseDown__: any,
	__mouseUp__: any,
	__mouseMove__: any,
	__parentDom__: HTMLElement,
	__position__: Position
}

const directive: Directive = {
	mounted: (el: ElType, binding: DirectiveBinding) => {
		setParentDom(el, binding, false)
		// 子级元素位置处理
		// 1. 获取父子元素当前位置
		let parentDomRect: DOMRect
		let elDomRect: DOMRect
		let mouseData: Mouse = {
			down: {x: 0, y: 0},
			move: {x: 0, y: 0}
		}
		let mouseDown: boolean = false
		el.__position__ = {
			x: 0,
			y: 0
		}
		let bodyUserSelect: string = 'text'

		function handleMouseDown(e: MouseEvent) {
			if (e.button !== 0) {
				return
			}
			mouseData.down = {
				x: e.clientX,
				y: e.clientY
			}
			mouseDown = true
			parentDomRect = el.__parentDom__.getBoundingClientRect()
			elDomRect = el.getBoundingClientRect()
			bodyUserSelect = document.querySelector('body')!.style.userSelect
			document.querySelector('body')!.style.userSelect = "none"
		}

		function handleMouseMove(e: MouseEvent) {
			if (!mouseDown) {
				return
			}
			mouseData.move = {
				x: e.clientX,
				y: e.clientY
			}
			setPosition()
		}

		function handleMouseUp(e: MouseEvent) {
			if (mouseDown) {
				mouseDown = false
				document.querySelector('body')!.style.userSelect = bodyUserSelect
			}
		}

		// 用于设置el元素的Position位置
		function setPosition() {
			// 通过几何图形计算更佳，我就是通过几何画图计算出来的当前数据，使用者可以自行计算，得到这两个值
			const x = mouseData.move.x + elDomRect.x - parentDomRect.x - mouseData.down.x
			const y = mouseData.move.y + elDomRect.y - parentDomRect.y - mouseData.down.y
			// 进行x,y坐标边界处理判断
			if (x < 0) {
				el.__position__.x = 0
			} else if (x > parentDomRect.width - elDomRect.width) {
				el.__position__.x = parentDomRect.width - elDomRect.width
			} else {
				el.__position__.x = x
			}
			if (y < 0) {
				el.__position__.y = 0
			} else if (y > parentDomRect.height - elDomRect.height) {
				el.__position__.y = parentDomRect.height - elDomRect.height
			} else {
				el.__position__.y = y
			}
			// 渲染到真实dom属性上
			el.style.cssText += `
        position: absolute;
        z-index: 100;
        left: ${el.__position__.x}px;
        top: ${el.__position__.y}px;
      `
		}

		el.__mouseDown__ = handleMouseDown
		el.__mouseMove__ = handleMouseMove
		el.__mouseUp__ = handleMouseUp
		// 2. 监听拖拽事件
		el.addEventListener('mousedown', el.__mouseDown__)
		document.addEventListener('mousemove', el.__mouseMove__)
		document.addEventListener('mouseup', el.__mouseUp__)
	},
	updated(el, binding) {
		setParentDom(el, binding, true)
	},
	beforeUnmount(el: ElType) {
		// 避免重复开销，卸载所有的监听
		// 解决问题：多次创建新的实例 =》 监听不取消 =》 同时存在多个无用的监听，导致页面性能变差
		document.removeEventListener('mousedown', el.__mouseDown__)
		document.removeEventListener('mousemove', el.__mouseMove__)
		document.removeEventListener('mouseup', el.__mouseUp__)
	}
}

// 设置parentDom，供mounted和update使用
function setParentDom(el: ElType, binding: DirectiveBinding, updated: boolean) {

	const array = [
		{name: 'father', dom: el.parentElement}
	]

	// 获取父级元素
	let parentDom: HTMLElement | HTMLBodyElement
	// 以下if操作用于确保一定有一个parentDom
	if (binding.value) {
		const findArr = array.find((arr) => {
			return arr.name === binding.value
		})
		if (findArr && findArr.dom) {
			parentDom = findArr.dom
		} else {
			parentDom = document.querySelector(binding.value) || array[0].dom as HTMLElement || array[1].dom
		}
	} else {
		parentDom = array[0].dom as HTMLElement || array[1].dom
	}
	const parentDomRect = parentDom.getBoundingClientRect()
	const elDomRect = el.getBoundingClientRect()
	// 把以前的样式重置一下
	if (el.__parentDom__) {
		el.__parentDom__.style.position = 'static'
	}
	el.__parentDom__ = parentDom
	el.__parentDom__.style.position = 'relative'

	if (updated) {
		el.__position__ = {
			x: elDomRect.x - parentDomRect.x,
			y: elDomRect.y - parentDomRect.y
		}
		// return
		el.style.cssText += `
      position: absolute;
      z-index: 100;
      left: ${el.__position__.x}px;
      top: ${el.__position__.y}px;
    `
	}
}

export default directive